package fileoperate

import (
	"archive/zip"
	"bytes"
	"context"
	"errors"
	"fmt"
	"github.com/nwaples/rardecode"
	"github.com/spf13/afero"
	"gitlab.local/DO-module/new-filemanage-module/src/common"
	"gitlab.local/DO-module/new-filemanage-module/src/controller/errcode"
	fileopreate2 "gitlab.local/TerraMaster/tos-modules/fileoperate"
	"io"
	"io/fs"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/gogf/gf/errors/gerror"
	"gitlab.local/golibrary/linux"
	"gitlab.local/golibrary/user"
	"gitlab.local/golibrary/utils"
	"golang.org/x/text/encoding"
	"golang.org/x/text/encoding/charmap"
	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/encoding/korean"
	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/encoding/traditionalchinese"
	"golang.org/x/text/encoding/unicode"
	"golang.org/x/text/transform"
)

var CodingMap = map[string]*encoding.Decoder{
	"GB18030":    simplifiedchinese.GB18030.NewDecoder(),
	"GBK":        simplifiedchinese.GBK.NewDecoder(),
	"HZGB2312":   simplifiedchinese.HZGB2312.NewDecoder(),
	"Big5":       traditionalchinese.Big5.NewDecoder(),
	"UTF8":       unicode.UTF8.NewDecoder(),
	"UTF8BOM":    unicode.UTF8BOM.NewDecoder(),
	"ISO8859_6E": charmap.ISO8859_6E.NewDecoder(),
	"ISO8859_6I": charmap.ISO8859_6I.NewDecoder(),
	"ISO8859_8E": charmap.ISO8859_8E.NewDecoder(),
	"ISO8859_8I": charmap.ISO8859_8I.NewDecoder(),
	"EUCJP":      japanese.EUCJP.NewDecoder(),
	"ISO2022JP":  japanese.ISO2022JP.NewDecoder(),
	"ShiftJIS":   japanese.ShiftJIS.NewDecoder(),
	"EUCKR":      korean.EUCKR.NewDecoder(),
}

var TaskTerminationError = errors.New("task termination") //任务终止错误

//type FileOperationRunStatus uint8 //文件操作类型

const (
	NotRunning      fileopreate2.FileOperationRunStatus = iota //未运行
	Fail                                                       //失败
	SizeCalculation                                            //大小计算中
	Running                                                    //运行中
	Completed                                                  //完成
	Canceling                                                  //取消
	CancelComplete                                             //取消完成
)

type FileOperationTask struct {
	taskName          string //任务名称
	TaskId            int64  //任务ID
	coding            string //编码
	computing         bool   //是否正在计算进度
	safeBoxSrc        string //保险箱路径
	recentlyPaths     []string
	uname             string                              //操作用户
	Type              fileopreate2.FileOperation          //任务类型
	paths             []string                            //来源路径s
	ToPath            string                              //目标文件夹路径
	Size              int64                               //任务总大小(B)
	CurrentSize       int64                               //当前已完成大小(B)
	FileTotal         int64                               //文件总数量
	ctx               context.Context                     //任务上下文
	cancelFunc        context.CancelFunc                  //任务取消函数
	cancelInformation chan struct{}                       //取消通知
	Rate              float32                             //进度百分比
	Status            fileopreate2.FileOperationRunStatus //任务状态
	Err               error                               //错误信息
}

func (f *FileOperate) getTask(taskId int64) *FileOperationTask {
	f.fileOperationRWMutex.RLock()
	task, b := f.fileOperationTask[taskId]
	f.fileOperationRWMutex.RUnlock()
	if b {
		return task
	}
	return nil
}

func (f *FileOperate) getUserAllTask(uname string) []*FileOperationTask {
	var tasks []*FileOperationTask

	f.fileOperationRWMutex.RLock()
	for _, v := range f.fileOperationTask {
		if v.uname == uname {
			tasks = append(tasks, v)
		}
	}
	f.fileOperationRWMutex.RUnlock()

	return tasks
}

// calculateProgress 计算任务进度
func (f *FileOperate) calculateProgress(task *FileOperationTask) error {
	if task.Status != Running { //只有运行中的任务才能计算进度
		return nil
	}
	if task.computing { //一个任务同时只能运行一个计算进度
		return errors.New("calculateProgress")
	}
	task.computing = true
	defer func() {
		task.computing = false
	}()

	if task.Type == fileopreate2.Compression || task.Type == fileopreate2.Decompress {
		return f.zipElegantProgress(&task.Rate, task.CurrentSize, task.Size)
	}

	//计算剩余时间
	tmp := f.elegantProgress(task.uname, task.ctx, &task.Rate, &task.CurrentSize, task.Size, task.recentlyPaths...)
	return tmp
}

func (f *FileOperate) zipElegantProgress(rate *float32, currentSize int64, total int64) error {
	r := f.calculate(float64(currentSize), float64(total))
	if *rate == 100 {
		return nil
	}
	if r > *rate { //防止进度回退
		*rate = r
	}

	return nil
}

// elegantProgress 进度优雅化
func (f *FileOperate) elegantProgress(name string, ctx context.Context, rate *float32, currentSize *int64, total int64, paths ...string) error {
	cancel, cancelFunc := context.WithCancel(ctx)
	defer cancelFunc()

	var (
		err  error
		size int64
	)

	_ = utils.SwitchUserRight(name)

	for _, path := range paths {
		select {
		case <-cancel.Done():
			//任务终止
			return TaskTerminationError
		default:
		}

		stat, e := os.Stat(path)
		if e != nil {
			//可能没有权限，跳过
			continue
		}

		size += stat.Size()
		r := f.calculate(float64(size), float64(total))
		if *rate == 100 {
			break
		}
		if r > *rate { //防止进度回退
			*rate = r
			*currentSize = size
		}

		if !stat.IsDir() {
			continue
		}

		err = filepath.WalkDir(path, func(path string, d fs.DirEntry, e error) error {
			select {
			case <-cancel.Done():
				//任务终止
				return TaskTerminationError
			default:
			}

			if e != nil {
				//可能没有权限，跳过
				return nil
			}
			info, e := d.Info()
			if e != nil {
				//可能没有权限，跳过
				return nil
			}

			size += info.Size()
			r = f.calculate(float64(size), float64(total))
			if *rate == 100 {
				return errors.New("task completed")
			}
			if r > *rate { //防止进度回退
				*rate = r
				*currentSize = size
			}

			return nil
		})
		if err != nil {
			break
		}
	}

	return err
}

func (f *FileOperate) calculate(divisor, dividend float64) float32 {
	var err error
	rate := 99.00 //默认99.00

	if divisor < dividend { //如果进度小于100则计算进度
		rate, err = strconv.ParseFloat(fmt.Sprintf("%.2f", divisor/dividend*100.00), 32)
		if err != nil {
			return 0
		}
	}

	return float32(rate)
}

// removeFileOperationTask 移除任务
func (f *FileOperate) removeFileOperationTask(taskId int64) error {
	if err := f.stopFileOperationTask(taskId); err != nil {
		return gerror.Wrap(err, "removeFileOperationTask")
	}

	f.fileOperationRWMutex.Lock()
	delete(f.fileOperationTask, taskId)
	f.fileOperationRWMutex.Unlock()

	return nil
}

// stopFileOperationTask 停止任务
func (f *FileOperate) stopFileOperationTask(taskId int64) error {
	f.fileOperationRWMutex.RLock()
	task, b := f.fileOperationTask[taskId]
	f.fileOperationRWMutex.RUnlock()
	if !b {
		return errors.New("task not found")
	}

	switch task.Status {
	case Completed, CancelComplete, Canceling: //完成、取消完成、取消完成的任务不需要停止
		return nil
	}

	if task.Status != Fail {
		if task.Status != SizeCalculation && task.Status != Running {
			return errors.New("operation not allowed in current state")
		}
		task.Status = Canceling
		task.cancelFunc()
		<-task.cancelInformation //等待任务执行完成
	}

	task.Status = CancelComplete

	return nil
}

// RunFileOperationTask 运行文件操作任务
func (f *FileOperate) RunFileOperationTask(taskParams fileopreate2.TaskParams) (int64, error) {
	if taskParams.TType == fileopreate2.Move {
		if taskParams.SafeBoxSrc == "" {
			taskParams.Paths = f.filterDirectory(taskParams.Paths, taskParams.ToPath)
		}

		if len(taskParams.Paths) == 0 {
			//没有需要移动的文件
			return 0, nil
		}
	}

	ctx, cancelFunc := context.WithCancel(context.Background())
	taskId := time.Now().UnixMicro()

	fileOperationTask := &FileOperationTask{
		TaskId:            taskId,
		uname:             taskParams.Uname,
		Type:              taskParams.TType,
		paths:             taskParams.Paths,
		ToPath:            taskParams.ToPath,
		FileTotal:         int64(taskParams.FileTotal),
		ctx:               ctx,
		cancelFunc:        cancelFunc,
		safeBoxSrc:        taskParams.SafeBoxSrc,
		recentlyPaths:     make([]string, 0),
		coding:            taskParams.Coding,
		cancelInformation: make(chan struct{}),
	}

	if taskParams.Size > 0 {
		fileOperationTask.Size = taskParams.Size
	}
	f.fileOperationRWMutex.Lock()
	f.fileOperationTask[taskId] = fileOperationTask
	f.fileOperationRWMutex.Unlock()

	// 任务监视器清理已完成、已失败或已取消的任务
	go func() {
		var exit bool
		for {
			time.Sleep(5 * time.Second)
			for _, s := range []fileopreate2.FileOperationRunStatus{Completed, Fail, CancelComplete} {
				if s == fileOperationTask.Status {
					time.Sleep(3 * time.Second)
					delete(f.fileOperationTask, taskId)
					exit = true
					break
				}
			}
			if exit {
				break
			}
		}
	}()

	// 执行任务
	go func() {
		var (
			err error
			src string
		)
		switch taskParams.TType {
		case fileopreate2.Copy:
			err = f.cpFile(fileOperationTask, ctx)
		case fileopreate2.Move:
			err = f.mvFile(fileOperationTask, ctx)
		case fileopreate2.Compression:
			src, err = f.zipFile(fileOperationTask, ctx)
		case fileopreate2.Decompress:
			if taskParams.UnType == fileopreate2.Rar {
				src, err = f.unrarFile(fileOperationTask, ctx)
			} else {
				src, err = f.unzipFile(fileOperationTask, ctx)
			}
		case fileopreate2.Upload:
			err = f.uploadFile(fileOperationTask, ctx)
		default:
			err = gerror.New("unknown file operation type")
		}

		if err != nil {
			switch fileOperationTask.Status {
			case SizeCalculation, Running:
				fileOperationTask.Status = Fail
				fileOperationTask.Err = err
			}
		} else {
			fileOperationTask.Status = Completed
			fileOperationTask.Rate = 100
			fileOperationTask.CurrentSize = fileOperationTask.Size
			if f.recentlyFunc != nil {
				switch taskParams.TType {
				case fileopreate2.Copy, fileopreate2.Move:
					for _, v := range fileOperationTask.recentlyPaths {
						stat, err := os.Stat(v)
						if err == nil && !stat.IsDir() {
							f.recentlyFunc(taskParams.Uname, v)
						}
					}
				case fileopreate2.Compression:
					f.recentlyFunc(taskParams.Uname, src)
				}
			}
		}

		cancelFunc()
		fileOperationTask.cancelInformation <- struct{}{}
	}()

	return taskId, nil
}

func (f *FileOperate) filterDirectory(paths []string, distDir string) []string {
	isoroot := linux.GetMainVolume()
	if isoroot != "" {
		if strings.HasPrefix(distDir, "/home/") {
			distDir = strings.Replace(distDir, "home", filepath.Join(isoroot, "User"), 1)
		}
		for k, v := range paths {
			//转换home目录路径,同卷下移动
			if strings.HasPrefix(v, "/home/") {
				paths[k] = strings.Replace(v, "home", filepath.Join(isoroot, "User"), 1)
			}
		}
	}

	var pathArr []string
	for _, v := range paths {
		if filepath.Dir(v) == distDir { //同目录不操作
			continue
		}
		pathArr = append(pathArr, v)
	}

	return pathArr
}

// mvFile 移动文件(如果同卷移动则使用mv，否则使用cp，然后在rm)
func (f *FileOperate) mvFile(task *FileOperationTask, ctx context.Context) error {

	//先计算任务大小
	task.Status = SizeCalculation

	_ = utils.SwitchUserRight(task.uname)
	err := f.statistics(ctx, &task.FileTotal, &task.Size, false, task.paths...)
	if err != nil {
		return gerror.Wrap(err, "mvFile")
	}

	// 目标位置容量超限判断
	overexceed := f.VolumeUserQuota(task.ToPath, task.uname, task.Size)
	if overexceed {
		task.Status = Fail
		task.Err = gerror.NewCode(errcode.InsufficientHardDiskCapacity)
		return task.Err
	}

	task.Status = Running

	for _, v := range task.paths {
		cancel, cloneFunc := context.WithCancel(ctx)
		defer cloneFunc()
		p, err := utils.AutoFolderName(filepath.Join(task.ToPath, filepath.Base(v)))
		if err != nil {
			return gerror.Wrap(err, "mvFile1")
		}
		task.recentlyPaths = append(task.recentlyPaths, p)

		done := make(chan error, 1)
		source := utils.StrSpecialLetters(v)
		aim := utils.StrSpecialLetters(p)
		go func() {
			cmd := fmt.Sprintf(`mv %s %s;`, source, aim)
			_, err = utils.ShellUserContextExec(cancel, task.uname, cmd)
			done <- err
		}()
		select {
		case <-cancel.Done():
			_ = os.Remove(p)
			return nil //任务终止
		case err := <-done:

			if err != nil {
				//拷贝完成或发生错误
				_ = os.Remove(p)
				return err
			}
		}
		continue

	}
	return nil
}

// cpFile 复制文件
// task 任务信息
// ctx 上下文
func (f *FileOperate) cpFile(task *FileOperationTask, ctx context.Context) error {

	//先计算任务大小
	task.Status = SizeCalculation

	_ = utils.SwitchUserRight(task.uname)
	err := f.statistics(ctx, &task.FileTotal, &task.Size, false, task.paths...)
	if err != nil {
		return gerror.Wrap(err, "cpFile")
	}

	// 目标位置容量超限判断
	overexceed := f.VolumeUserQuota(task.ToPath, task.uname, task.Size)
	if overexceed {
		task.Status = Fail
		task.Err = gerror.NewCode(errcode.InsufficientHardDiskCapacity)
		return task.Err
	}

	task.Status = Running

	for _, v := range task.paths {
		cancel, cancelFunc := context.WithCancel(ctx)
		defer cancelFunc()
		p, _ := utils.AutoFolderName(filepath.Join(task.ToPath, filepath.Base(v)))
		task.recentlyPaths = append(task.recentlyPaths, p)
		done := make(chan error, 1)
		go func() {
			done <- f.copyHandle(v, task.ToPath, task.uname, ctx)
		}()
		select {
		case <-cancel.Done():
			return nil //任务终止
		case err := <-done:
			// 拷贝完成或发生错误
			if err != nil {
				return err
			}
		}

	}

	return err
}

// copyHandle
// @Description 处理copy代码
// @Param src 来源文件夹
// @Param dst 目的地文件夹
// @Param uname 用户名
// @Param con 上下文
// @Return err
func (f *FileOperate) copyHandle(src, dst, uname string, con context.Context) (err error) {
	cancel, cloneFunc := context.WithCancel(con)
	defer cloneFunc()
	if isDir, err := afero.IsDir(f.fs, src); isDir {
		if err != nil {
			return gerror.Wrap(err, "cpFile1")
		}
		dir, _ := afero.ReadDir(f.fs, src)
		newFolder, _ := utils.AutoFolderName(filepath.Join(dst, filepath.Base(src)))
		_ = f.fs.MkdirAll(newFolder, os.ModePerm)
		stat, _ := f.fs.Stat(src)
		stat.ModTime()
		err := f.fs.Chtimes(newFolder, stat.ModTime(), stat.ModTime())

		for _, info := range dir {
			if info.IsDir() {
				if err = f.copyHandle(filepath.Join(src, info.Name()), newFolder, uname, cancel); err != nil {
					return err
				}
			} else {
				err = f.copyFileHandle(filepath.Join(src, info.Name()), filepath.Join(newFolder, info.Name()), uname, cancel)
				if err != nil {
					return err
				}
			}
		}
	} else {
		p, err := utils.AutoFolderName(filepath.Join(dst, filepath.Base(src)))
		if err != nil {
			return gerror.Wrap(err, "cpFile1")
		}
		err = f.copyFileHandle(src, p, uname, cancel)
		if err != nil {
			return err
		}
	}
	return nil
}

// copyFileHandle
// @Description 拷贝文件具体执行
// @Param src 来源文件
// @Param dst 目标
// @Param uname 用户名
// @Param con 上下文
// @Return err
func (f *FileOperate) copyFileHandle(src, dst, uname string, con context.Context) (err error) {
	cancel, cloneFunc := context.WithCancel(con)
	defer cloneFunc()

	done := make(chan error, 1)
	go func() {
		cmd := fmt.Sprintf(`cp -p %s %s;`, utils.StrSpecialLetters(src), utils.StrSpecialLetters(dst))
		_, err = utils.ShellUserContextExec(cancel, uname, cmd)
		done <- err
	}()
	select {
	case <-cancel.Done():
		_ = os.Remove(dst)

		return nil //任务终止
	case err := <-done:
		// 拷贝完成或发生错误
		if err != nil {
			return err
		}
	}
	return nil
}

// UnRarFile 解压
func (f *FileOperate) unrarFile(task *FileOperationTask, ctx context.Context) (string, error) {
	rarSrc := task.paths[0]
	task.Status = Running
	fileName := filepath.Base(rarSrc)
	index := strings.LastIndex(fileName, ".")
	if index > 0 {
		fileName = fileName[:index]
	}

	out, err := utils.AutoFolderName(filepath.Join(task.ToPath, fileName))
	if err != nil {
		return "", gerror.Wrap(err, "Unrar")
	}
	if err = os.MkdirAll(out, 0755); err != nil {
		return "", gerror.Wrap(err, "Unrar")
	}
	err = extractRAR(rarSrc, out, task, ctx)
	if err != nil {
		go os.RemoveAll(out)
		return out, gerror.Wrap(err, "UnRarCompress4")
	}

	lookup, err := user.Lookup(task.uname)
	if err != nil {
		return out, gerror.Wrap(err, "UnRarCompress5")
	}
	_, _ = utils.ShellExec(fmt.Sprintf(`chown -R '%s:%s' '%s'`, lookup.Uid, lookup.Gid, out))
	//继承父级权限
	_ = f.aclService.InheritedPermissions(out, "", false)

	return out, nil
}

func extractRAR(rarFilePath string, outputDir string, task *FileOperationTask, ctx context.Context) error {
	// 打开RAR文件
	rarFile, err := os.Open(rarFilePath)
	if err != nil {
		return gerror.Wrap(err, "File open fail")
	}
	defer rarFile.Close()
	stat, err := rarFile.Stat()
	stat.Size()
	// 创建RAR解码器
	rarReader, err := rardecode.NewReader(rarFile, "")
	if err != nil {
		return gerror.Wrap(err, "File reader fail")
	}

	// 创建输出目录
	err = os.MkdirAll(outputDir, 0755)
	if err != nil {
		return gerror.Wrap(err, "File create dir fail")
	}

	for {
		cancel, cancelFunc := context.WithCancel(ctx)
		defer cancelFunc()
		header, err := rarReader.Next()
		if err != nil {
			if err.Error() == "EOF" {
				break
			}
			return gerror.Wrap(err, "File next fail")
		}

		// 判断文件是否为文件夹
		if header.IsDir {
			continue
		}
		task.FileTotal++
		// 获取文件内容
		content, err := ioutil.ReadAll(rarReader)
		if err != nil {
			return gerror.Wrap(err, "File read fail")
		}
		// 判断文件内容的编码格式
		decoding := detectEncoding(content)

		// 解码文件内容
		decodedContent, err := decodeContent(content, decoding)
		if err != nil {
			return gerror.Wrap(err, "File decode fail")
		}

		// 构建输出文件路径
		outputFilePath := filepath.Join(outputDir, header.Name)
		if err = os.MkdirAll(filepath.Dir(outputFilePath), 0755); err != nil {
			return gerror.Wrap(err, "File create fail")
		}
		newFile, err := os.OpenFile(outputFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, header.Mode())
		if err != nil {
			return gerror.Wrap(err, "unzipFile7")
		}
		_ = newFile.Chmod(header.Mode())

		task.recentlyPaths = append(task.recentlyPaths, outputFilePath)

		done := make(chan error, 1)
		// 写入文件内容
		go func() {
			newReader := bytes.NewReader([]byte(decodedContent))
			_, err = io.Copy(&ObservableWriter{Writer: newFile, OnWritten: func(data []byte) {
				task.CurrentSize += int64(len(data))
			}}, newReader)
			_ = newFile.Close()
			done <- err
		}()

		select {
		case <-cancel.Done():
			_ = os.Remove(outputFilePath)
			return nil //任务终止
		case err := <-done:
			// 拷贝完成或发生错误
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func detectEncoding(content []byte) string {
	if common.IsUtf8(content) == true {
		return "UTF-8"
	}
	return "GBK"
}

func decodeContent(content []byte, encoding string) (string, error) {
	switch strings.ToUpper(encoding) {
	case "UTF-8":
		return string(content), nil
	case "GBK":

		decoder := simplifiedchinese.GBK.NewDecoder()
		decodedContent, err := decoder.String(string(content))
		if err != nil {
			return "", err
		}
		return decodedContent, nil
	// 添加其他可能的编码方式
	default:
		return "", gerror.New("Unsupported encoding")
	}
}

// uploadFile 上传文件任务
func (f *FileOperate) uploadFile(task *FileOperationTask, ctx context.Context) error {
	task.Status = Running
	done := make(chan error, 1)
	var err error
	cancel, cloneFunc := context.WithCancel(ctx)
	defer cloneFunc()
	go func() {
		for {
			rate, err := strconv.ParseFloat(fmt.Sprintf("%.2f", float64(task.CurrentSize)/float64(task.Size)*100.00), 32)
			if err != nil {
				done <- err
				return
			}
			task.Rate = float32(rate)
			if task.Size == task.CurrentSize {
				done <- nil
				return
			}
		}
	}()
	select {
	case <-cancel.Done():
		return nil //任务终止
	case err := <-done:
		// 拷贝完成或发生错误
		if err != nil {
			return err
		}
	}

	return err
}

// UnzipFile 解压
func (f *FileOperate) unzipFile(task *FileOperationTask, ctx context.Context) (string, error) {
	zipSrc := task.paths[0]
	//先计算任务大小
	task.Status = SizeCalculation
	err := f.ZipActualSize(zipSrc, ctx, &task.Size, &task.FileTotal)
	if err != nil {
		return "", gerror.Wrap(err, "unzipFile")
	}

	task.Status = Running

	r, err := zip.OpenReader(zipSrc)
	if err != nil {
		return "", gerror.Wrap(err, "unzipFile1")
	}
	defer r.Close()

	fileName := filepath.Base(zipSrc)
	index := strings.LastIndex(fileName, ".")
	if index > 0 {
		fileName = fileName[:index]
	}

	dst, err := utils.AutoFolderName(filepath.Join(task.ToPath, fileName))
	if err != nil {
		return "", gerror.Wrap(err, "unzipFile2")
	}

	if err = os.MkdirAll(dst, os.ModePerm); err != nil {
		return "", gerror.Wrap(err, "unzipFile3")
	}

	for _, v := range r.File {
		cancel, cancelFunc := context.WithCancel(ctx)
		defer cancelFunc()
		var decodeName string //文件名
		if v.Flags == 0 {
			//如果标致位是0 则是默认的本地编码 默认为GB18030
			str, err := io.ReadAll(transform.NewReader(bytes.NewReader([]byte(v.Name)), simplifiedchinese.GB18030.NewDecoder()))
			if err != nil {
				return dst, gerror.Wrap(err, "unzipFile4")
			}
			decodeName = string(str)
		} else {
			//如果标志为是 1 << 11也就是 2048 则是utf-8 编码
			decodeName = v.Name
		}

		src := filepath.Join(dst, decodeName)
		fileInfo := v.FileInfo()
		if fileInfo.IsDir() {
			if err = os.MkdirAll(src, os.ModePerm); err != nil {
				return dst, gerror.Wrap(err, "unzipFile5")
			}
			_ = os.Chmod(src, fileInfo.Mode())
			task.recentlyPaths = append(task.recentlyPaths, src)
			continue
		}

		if err = os.MkdirAll(filepath.Dir(src), os.ModePerm); err != nil {
			return dst, gerror.Wrap(err, "unzipFile6")
		}

		newFile, err := os.OpenFile(src, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileInfo.Mode())
		if err != nil {
			return dst, gerror.Wrap(err, "unzipFile7")
		}
		_ = newFile.Chmod(fileInfo.Mode())

		content, err := v.Open()
		if err != nil {
			_ = newFile.Close()
			return dst, gerror.Wrap(err, "unzipFile8")
		}
		task.recentlyPaths = append(task.recentlyPaths, src)

		done := make(chan error, 1)
		go func() {
			_, err = io.Copy(&ObservableWriter{Writer: newFile, OnWritten: func(data []byte) {
				task.CurrentSize += int64(len(data))
			}}, content)
			_ = newFile.Close()
			_ = content.Close()
			done <- err
		}()

		select {
		case <-cancel.Done():
			_ = os.Remove(src)
			return dst, nil //任务终止
		case err := <-done:
			// 拷贝完成或发生错误
			if err != nil {
				return "", err
			}
		}
		continue

	}

	lookup, err := user.Lookup(task.uname)
	if err != nil {
		return dst, gerror.Wrap(err, "unzipFile10")
	}
	_, _ = utils.ShellWithContext(ctx, fmt.Sprintf(`chown -R '%s:%s' '%s'`, lookup.Uid, lookup.Gid, dst))
	//继承父级权限
	_ = f.aclService.InheritedPermissions(dst, "", false)

	return dst, nil
}

// ZipActualSize 获取压缩文件实际大小
func (f *FileOperate) ZipActualSize(zipSrc string, ctx context.Context, size, total *int64) error {
	if size == nil && total == nil {
		return nil
	}

	r, err := zip.OpenReader(zipSrc)
	if err != nil {
		return err
	}

	var cancel context.Context

	if ctx != nil {
		var cancelFunc context.CancelFunc
		cancel, cancelFunc = context.WithCancel(ctx)
		defer cancelFunc()
	}

	for _, v := range r.File {
		if cancel != nil {
			select {
			case <-cancel.Done():
				return TaskTerminationError //任务终止
			default:
			}
		}
		if size != nil {
			*size += v.FileInfo().Size()
		}
		if total != nil {
			*total++
		}
	}

	_ = r.Close()

	return nil
}

// ZipFile 压缩文件
func (f *FileOperate) zipFile(task *FileOperationTask, ctx context.Context) (string, error) {
	//先计算任务大小
	task.Status = SizeCalculation

	_ = utils.SwitchUserRight(task.uname)
	if err := f.statistics(ctx, &task.FileTotal, &task.Size, true, task.paths...); err != nil {
		return "", gerror.Wrap(err, "zipFile")
	}

	// 目标位置容量超限判断
	overexceed := f.VolumeUserQuota(task.ToPath, task.uname, task.Size)
	if overexceed {
		task.Status = Fail
		task.Err = gerror.NewCode(errcode.InsufficientHardDiskCapacity)
		return "", task.Err
	}

	task.Status = Running

	//名称方案
	//如果只有一个文件或目录，则使用这个文件或目录的名称加.zip，如果压缩文件数量大于2，这使用输出文件目录名称
	fileName := ""
	if len(task.paths) == 1 {
		baseName := filepath.Base(task.paths[0])
		fileName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
		if fileName == "" {
			fileName = baseName
		}
	} else {
		fileName = filepath.Base(task.ToPath)
	}

	src, e := utils.AutoFolderName(filepath.Join(task.ToPath, fileName) + ".zip")
	if e != nil {
		return "", gerror.Wrap(e, "zipFile1")
	}
	task.taskName = filepath.Base(src)

	file, e := os.OpenFile(src, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
	if e != nil {
		return "", gerror.Wrap(e, "zipFile2")
	}

	e = f.zip(task, ctx, file, src)
	_ = file.Close()
	if e != nil {
		return src, gerror.Wrap(e, "zipFile3")
	}

	//修改文件拥有者
	_ = f.aclService.Chown(src, task.uname)
	//继承父级acl权限
	_ = f.aclService.InheritedPermissions(src, "", false)

	return src, nil
}

// statistics 计算文件数量与大小
func (f *FileOperate) statistics(ctx context.Context, total *int64, size *int64, ignoreDir bool, paths ...string) error {
	cancel, cancelFunc := context.WithCancel(ctx)
	defer cancelFunc()

	for _, v := range paths {
		select {
		case <-cancel.Done():
			return TaskTerminationError //任务终止
		default:
		}

		stat, err := os.Stat(v)
		if err != nil {
			continue
		}

		if !stat.IsDir() {
			*size += stat.Size()
			*total++
			continue
		}

		if !ignoreDir {
			//目录也算一个文件
			*size += stat.Size()
			*total++
		}

		err = filepath.WalkDir(v, func(path string, d fs.DirEntry, err error) error {
			select {
			case <-cancel.Done():
				return TaskTerminationError //任务终止
			default:
			}
			if err != nil {
				//可能没有权限，跳过
				return nil
			}
			info, err := d.Info()
			if err != nil {
				return err
			}
			if !d.IsDir() {
				*size += info.Size()
				*total++
				return nil
			}
			if !ignoreDir {
				//目录也算一个文件
				*size += info.Size()
				*total++
			}
			return nil
		})

		if err != nil {
			break
		}
	}

	return nil
}

// Zip zip压缩文件
// paths 压缩绝对路径s
// dst 输出io
// coding 文件名编码
func (f *FileOperate) zip(task *FileOperationTask, ctx context.Context, dst io.Writer, newPaht string) error {
	if len(task.paths) == 0 {
		return errors.New("paths is empty")
	}

	var code *encoding.Decoder
	if task.coding != "" {
		code = CodingMap[task.coding]
	}
	//创建压缩文件句柄
	zipWriter := zip.NewWriter(dst)
	defer zipWriter.Close()

	updateCurrentSize := func(b []byte) {
		task.CurrentSize += int64(len(b))
	}

	for _, v := range task.paths {
		cancel, cancelFunc := context.WithCancel(ctx)
		defer cancelFunc()

		vStat, err := os.Stat(v)
		if err != nil {
			return gerror.Wrap(err, "zip")
		}
		if !vStat.IsDir() {
			task.taskName = filepath.Base(v)
			done := make(chan error, 1)
			go func() {
				done <- f.runZip(v, vStat.Name(), code, zipWriter, updateCurrentSize, cancel)
			}()
			task.recentlyPaths = append(task.recentlyPaths, v)
			select {
			case <-cancel.Done():
				_ = os.Remove(newPaht)
				return TaskTerminationError //任务终止
			case err := <-done:
				// 拷贝完成或发生错误
				if err != nil {
					_ = os.Remove(newPaht)
				}
				return err
			}
		}

		err = filepath.WalkDir(v, func(path string, d fs.DirEntry, err error) error {

			if err != nil {
				return err
			}

			relativePath := strings.TrimPrefix(strings.TrimPrefix(path, filepath.Dir(v)), "/")

			if d.IsDir() {
				relativePath = fmt.Sprintf("%s%c", relativePath, os.PathSeparator)
			}

			err = f.runZip(path, relativePath, code, zipWriter, updateCurrentSize, cancel)
			if err != nil {
				_ = os.Remove(newPaht)
				return err
			}

			task.taskName = filepath.Base(path)
			task.recentlyPaths = append(task.recentlyPaths, path)
			return nil
		})
	}

	return nil
}

// runZip 执行压缩
// initialPath 根路径
// path 文件路径(只能是文件)
func (f *FileOperate) runZip(path, relativePath string, code *encoding.Decoder, zipWriter *zip.Writer, observeZip func(data []byte), sonCtx context.Context) error {
	// 文件夹创建完直接返回
	if strings.HasSuffix(relativePath, "/") {
		_, err := zipWriter.Create(relativePath)
		return err
	}

	file, err := os.Open(path)
	if err != nil {
		return gerror.Wrap(err, "runZip")
	}
	defer file.Close()
	stat, err := file.Stat()
	if err != nil {
		return gerror.Wrap(err, "runZip1")
	}
	// 获取文件头信息
	header, err := zip.FileInfoHeader(stat)
	if err != nil {
		return gerror.Wrap(err, "runZip2")
	}

	header.Name = relativePath

	// 文件名转码
	if code != nil {
		//如果标致位是0 则是默认的本地编码 默认为GB18030
		str, err := io.ReadAll(transform.NewReader(bytes.NewReader([]byte(header.Name)), code))
		if err != nil {
			return gerror.Wrap(err, "runZip3")
		}
		header.Name = string(str)
	}

	//设置zip的文件压缩算法(默认关闭)
	header.Method = zip.Store

	//创建压缩包头部信息
	writer, err := zipWriter.CreateHeader(header)
	if err != nil {
		return gerror.Wrap(err, "runZip4")
	}
	done := make(chan error, 1)

	go func() {
		_, err = io.Copy(&ObservableWriter{Writer: writer, OnWritten: observeZip}, file)
		done <- err
	}()

	select {
	case <-sonCtx.Done():
		return TaskTerminationError //任务终止
	case err := <-done:
		// 拷贝完成或发生错误
		return err
	}
}

type ObservableWriter struct {
	io.Writer
	OnWritten func(data []byte)
}

func (w *ObservableWriter) Write(p []byte) (int, error) {
	n, err := w.Writer.Write(p)
	if err == nil {
		w.OnWritten(p)
	}
	return n, err
}
